/* * ProtoReader.java * * Created on 18 September 2009, 13:08 * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package com.eas.proto; import com.eas.util.BinaryUtils; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.Date; import java.util.zip.ZipInputStream; /** * Provides a reader for Platypus protocol. * * @author pk, mg */ public class ProtoReader { private int currentTag; private int currentSize; private boolean isTagReady; private static final String INVALID_TAG_MSG = "Invalid tag"; private static final String INVALID_TAG_SIZE_MSG = "Invalid tag size"; private static final String UNEXPECTED_EOF_MSG = "Unexpected end of stream"; private DataInputStream stream; /** * Creates a new instance of ProtoReader * * @param aStream the tag stream from which to read tags. */ public ProtoReader(InputStream aStream) { stream = new DataInputStream(aStream); } /** * Gets the next tag from the input stream. * * @return the next available tag. * @throws java.io.IOException if an I/O error occurs. */ public int getNextTag() throws java.io.IOException { if (isTagReady) { skip(); } isTagReady = true; int tag; try { tag = stream.readUnsignedByte(); } catch (java.io.EOFException ex) { currentTag = CoreTags.TAG_EOF; currentSize = 0; return currentTag; } currentSize = stream.readInt(); currentTag = tag; return currentTag; } /** * Gets the current tag from the input stream. * * @return the current tag. */ public int getCurrentTag() { return currentTag; } /** * Gets the current tag size from the input stream. * * @return the current tag size. */ public int getCurrentTagSize() { return currentSize; } /** * Skips the data available in the stream up to the next tag. * * @throws java.io.IOException if an I/O error occurs. */ public void skip() throws java.io.IOException { if (!isTagReady) { getNextTag(); } stream.skipBytes(currentSize); isTagReady = false; } public void getSignature() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != CoreTags.TAG_SIGNATURE) { throw new ProtoReaderException("Invalid stream format: tag " + currentTag + " != TAG_SIGNATURE=" + CoreTags.TAG_SIGNATURE); } if (currentSize != CoreTags.SIGNATURE_SIZE) { throw new ProtoReaderException("Invalid stream format: size " + currentSize + " != SIGNATURE_SIZE=" + CoreTags.SIGNATURE_SIZE); } stream.skipBytes(CoreTags.SIGNATURE_SIZE); isTagReady = false; } /** * Reads the data from the current tag into a byte array. * * @param data the byte array to read data into. * @param data_size the number of bytes to read from input stream. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the size of the current * tag doesn't equal to data_size, or if an EOF is encountered while reading data. */ public void get(byte[] data, int data_size) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != data_size) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } int offset = 0; while (true) { int bytesRead = stream.read(data, offset, currentSize - offset); if (bytesRead < 0 || offset == currentSize) { break; } offset += bytesRead; } if (offset < currentSize) { throw new ProtoReaderException(UNEXPECTED_EOF_MSG); } isTagReady = false; } /** * Reads the data from the current tag, or the next tag, if not tag is * available yet, into a byte array. Also ensures, that the tag to read * data from is the expected tag. * * @param tag the expected tag. * @param data the byte array to read data into. * @param data_size the number of bytes to read from input stream. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if available tag is not * equal to the expected tag, if the size of the current tag is not equal * to data_size, or if an EOF is encountered while reading data. */ public void get(int tag, byte[] data, int data_size) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != data_size) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } int bytes_readen = stream.read(data, 0, currentSize); if (bytes_readen < currentSize) { throw new ProtoReaderException(UNEXPECTED_EOF_MSG); } isTagReady = false; } /** * Ensures that the available tag is the specified tag. * * @param tag the expected tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not zero. */ public void get(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(String.format(INVALID_TAG_MSG + ": %d instead of %d", currentTag, tag)); } if (currentSize != 0) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } isTagReady = false; } /** * Reads the signed byte from the expected tag value. * * @param tag the expected tag. * @return the signed byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not one byte. */ public byte getByte(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != 1/*sizeof(byte)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } byte val = stream.readByte(); isTagReady = false; return val; } /** * Reads the signed byte from the current tag. * * @return the signed byte value of the current tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not one byte. */ public byte getByte() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != 1/*sizeof(byte)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } byte val = stream.readByte(); isTagReady = false; return val; } /** * Reads the signed short (2 byte) value from the expected tag. * * @param tag the expected tag. * @return the signed short 2 byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 2 bytes. */ public short getShort(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != Short.SIZE / Byte.SIZE/*sizeof(short)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } short val = stream.readShort(); isTagReady = false; return val; } /** * Reads the signed short (2 byte) value from the expected tag. * * @return the signed short 2 byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 2 bytes. */ public short getShort() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != Short.SIZE / Byte.SIZE/*sizeof(short)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } short val = stream.readShort(); isTagReady = false; return val; } /** * Reads the signed float (4 byte) value from the expected tag. * * @param tag the expected tag. * @return the signed float 4 byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 4 bytes. */ public float getFloat(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != Float.SIZE / Byte.SIZE/*sizeof(float)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } float val = stream.readFloat(); isTagReady = false; return val; } /** * Reads the signed float (4 byte) value from the expected tag. * * @return the signed float 4 byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 4 bytes. */ public float getFloat() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != Float.SIZE / Byte.SIZE/*sizeof(float)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } float val = stream.readFloat(); isTagReady = false; return val; } /** * Reads the signed integer (4 byte) value from the expected tag. * * @param tag the expected tag. * @return the signed integer 4 byte value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 4 bytes. */ public int getInt(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != Integer.SIZE / Byte.SIZE/*sizeof(int)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } int val = stream.readInt(); isTagReady = false; return val; } /** * Reads the signed integer (4 byte) value from the current tag. * * @return the signed integer 4 byte value of the current tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 4 bytes. */ public int getInt() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != Integer.SIZE / Byte.SIZE/*sizeof(int)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } int val = stream.readInt(); isTagReady = false; return val; } /** * Reads the BigDecimal value from the expected tag. * * @param tag the expected tag. * @return the BigDecimal value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not the expected tag. */ public BigDecimal getBigDecimal(int tag) throws IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize < 4/*sizeof(int)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } byte[] repr = new byte[currentSize]; get(repr, currentSize); byte[] unscaled = Arrays.copyOf(repr, repr.length - Integer.SIZE / Byte.SIZE); int scaleVal = ((repr[unscaled.length + 0] << 24) + (repr[unscaled.length + 1] << 16) + (repr[unscaled.length + 2] << 8) + (repr[unscaled.length + 3])); BigInteger unscaledVal = new BigInteger(unscaled); BigDecimal val = new BigDecimal(unscaledVal, scaleVal); isTagReady = false; return val; } /** * Reads the BigDecimal value from the current tag. * * @return the signed integer 4 byte value of the current tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag. */ public BigDecimal getBigDecimal() throws IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize < 4/*sizeof(int)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } byte[] repr = new byte[currentSize]; get(repr, currentSize); byte[] unscaled = Arrays.copyOf(repr, repr.length - Integer.SIZE / Byte.SIZE); int scaleVal = ((repr[unscaled.length + 0] << 24) + (repr[unscaled.length + 1] << 16) + (repr[unscaled.length + 2] << 8) + (repr[unscaled.length + 3])); BigInteger unscaledVal = new BigInteger(unscaled); BigDecimal val = new BigDecimal(unscaledVal, scaleVal); isTagReady = false; return val; } /** * Reads the signed double (8 bytes) value from the expected tag. * * @param tag the expected tag. * @return the signed double 8 bytes value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public double getDouble(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != 8/*sizeof(double)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } double val = stream.readDouble(); isTagReady = false; return val; } /** * Reads the signed double (8 bytes) value from the current tag. * * @return the signed double 8 bytes value of the current tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public double getDouble() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != 8/*sizeof(double)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } double val = stream.readDouble(); isTagReady = false; return val; } /** * Reads the signed long (8 bytes) value from the expected tag. * * @param tag the expected tag. * @return the signed long 8 bytes value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public long getLong(int tag) throws IOException, com.eas.proto.ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } if (currentSize != 8/*sizeof(long)*/) { throw new ProtoReaderException(INVALID_TAG_SIZE_MSG); } long val = stream.readLong(); isTagReady = false; return val; } /** * Reads the signed long (8 bytes) value from the current tag. * * @return the signed long 8 bytes value of the current tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public long getLong() throws IOException, com.eas.proto.ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentSize != 8/*sizeof(long)*/) { throw new ProtoReaderException("Invalid tag size " + currentSize + ", expected 8"); } long val = stream.readLong(); isTagReady = false; return val; } /** Reads the Unix time value from the expected tag and create * java.util.Date object to represent date-time value. * * @param tag the expected tag. * @return the Date value. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public Date getDate(int tag) throws IOException, ProtoReaderException { long dateTimeVal = getLong(tag); return new Date(dateTimeVal); } /** Reads the Unix time value from the current tag and create * java.util.Date object to represent date-time value. * * @return the Date value. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is not 8 bytes. */ public Date getDate() throws IOException, ProtoReaderException { long dateTimeVal = getLong(); return new Date(dateTimeVal); } /** * Reads the Unicode string value from the expected tag. * * @param tag the expected tag. * @return the Unicode string value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is greater than the * number of bytes actually read. */ public String getString(int tag) throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } if (currentTag != tag) { throw new ProtoReaderException(INVALID_TAG_MSG); } byte[] data = new byte[currentSize]; get(data, currentSize); String val = new String(data, ProtoUtil.CHARSET_4_STRING_SERIALIZATION_NAME); isTagReady = false; return val; } /** * Reads the Unicode string value from the current tag. * * @return the Unicode string value of the tag. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the available tag is not * the expected tag, or the size of the available tag is greater than the * number of bytes actually read. */ public String getString() throws java.io.IOException, ProtoReaderException { if (!isTagReady) { getNextTag(); } byte[] data = new byte[currentSize]; get(data, currentSize); String val = new String(data, ProtoUtil.CHARSET_4_STRING_SERIALIZATION_NAME); isTagReady = false; return val; } /** * Returns the input stream to read the substream of the current tag. * * @return the input stream to read the substream. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException * if the tag following the * current tag is not TAG_STREAM, which violates current rules for writing * substreams to tagged streams. */ public InputStream getSubStream() throws IOException, ProtoReaderException { return new ByteArrayInputStream(getSubStreamData()); } /** * Returns data of the substream of the current tag as a byte array. * * @return the input stream to read the substream. * @throws java.io.IOException if an I/O error occurs. * @throws com.eas.proto.ProtoReaderException if the tag following the current tag is not TAG_STREAM, * which violates current rules for writing substreams to tagged streams. */ public byte[] getSubStreamData() throws IOException, ProtoReaderException { int tag = getNextTag(); if (tag != CoreTags.TAG_STREAM && tag != CoreTags.TAG_COMPRESSED_STREAM) { throw new ProtoReaderException(INVALID_TAG_MSG); } byte[] subStreamData = new byte[currentSize]; get(subStreamData, currentSize); if (tag == CoreTags.TAG_COMPRESSED_STREAM) { try (ZipInputStream zStream = new ZipInputStream(new ByteArrayInputStream(subStreamData))) { zStream.getNextEntry(); subStreamData = BinaryUtils.readStream(zStream, -1); zStream.read(subStreamData); } } return subStreamData; } /** * Breaks reading current stream and start reading a new stream. * * @param dataStream the new stream to read. */ public void reset(InputStream dataStream) { stream = new DataInputStream(dataStream); } }